package etxt2db.api;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import edu.cmu.minorthird.text.MutableTextLabels;
import edu.cmu.minorthird.text.RegexTokenizer;
import edu.cmu.minorthird.text.Span;
import etxt2db.corpus.CompleteTextBaseLoader;
import etxt2db.corpus.IgnoreTagsCompleteTextBaseLoader;
import etxt2db.corpus.MyTextBaseLoader;

/**
 * <p>
 * This class allows the evaluation of a model using recall and precision.
 * </p>
 *
 * @author Gon�alo Sim�es
 */
public class ClassificationEvaluator {
	
	private String beginTagString = "<";
	private String endTagString = ">";
	private boolean docPerFile = true;
	private MutableTextLabels taggedCorpus;
	private MutableTextLabels untaggedCorpus;
	private String corpusId = null;
	
	public ClassificationEvaluator(){
	}
	
	/**
     * Function that prints the result of recall and precision of a classification task
     * @param file A File containing the text you want to classify
     * @param annotator The classifier you will use
     * @param attributes The list containing the attributes you want to show
     */
	public void printEvaluationReport(File file, ClassificationModel annotator, List<String> attributes) 
		throws IOException, ParseException{
		loadDocuments(file,annotator);
		if(attributes == null){
			attributes = new ArrayList<String>(taggedCorpus.getTypes());
		}
		printRecall(taggedCorpus, untaggedCorpus, attributes);
		printPrecision(taggedCorpus, untaggedCorpus, attributes);
	}
	
	public float getRecall(File file, ClassificationModel annotator, List<String> attributes) 
		throws IOException, ParseException{
		loadDocuments(file,annotator);
		if(attributes == null){
			attributes = new ArrayList<String>(taggedCorpus.getTypes());
		}
		return getRecallValue(taggedCorpus, untaggedCorpus, attributes);
	}
	
	public float getPrecision(File file, ClassificationModel annotator, List<String> attributes) 
		throws IOException, ParseException{
		loadDocuments(file,annotator);
		if(attributes == null){
			attributes = new ArrayList<String>(taggedCorpus.getTypes());
		}
		return getPrecisionValue(taggedCorpus, untaggedCorpus, attributes);
	}
	
	public float getFmeasure(File file, ClassificationModel annotator, List<String> attributes) 
		throws IOException, ParseException{
		float recall = getRecall(file,annotator,attributes);
		float precision = getPrecision(file,annotator,attributes);
		
		return (2*recall*precision)/(recall+precision);
	}
	
	public float getFmeasure(File file, ClassificationModel annotator, List<String> attributes, float beta) 
		throws IOException, ParseException{
		float recall = getRecall(file,annotator,attributes);
		float precision = getPrecision(file,annotator,attributes);
		
		return (((beta*beta)+1)*recall*precision)/(recall+(beta*beta*precision));
	}
	
	private void loadDocuments(File file, ClassificationModel annotator)throws IOException, ParseException{
		if(corpusId == null || !corpusId.equals(file.getAbsolutePath())){
			System.out.println("Tagging for the first time");
			taggedCorpus = loadAnnotatedTestingDocument(file);
			untaggedCorpus = loadFlatTestingDocument(file);
			annotator.annotate(untaggedCorpus);
			corpusId=file.getAbsolutePath();
		}
	}
	
	private MutableTextLabels loadAnnotatedTestingDocument(File file) throws IOException, ParseException{
		CompleteTextBaseLoader baseLoader2 = new CompleteTextBaseLoader();
		if(!docPerFile){
			baseLoader2.setDocumentStyle(MyTextBaseLoader.DOC_PER_LINE);
		}
		baseLoader2.setBeginTag(beginTagString);
		baseLoader2.setEndTag(endTagString);
		baseLoader2.load(file,new RegexTokenizer("\\s*([0-9]+|[a-zA-Z��������������������]+|\\W)\\s*"));
		MutableTextLabels labels2 = baseLoader2.getLabels();
		return labels2;
	}
	
	private MutableTextLabels loadFlatTestingDocument(File file) throws IOException, ParseException{
		IgnoreTagsCompleteTextBaseLoader baseLoader2 = new IgnoreTagsCompleteTextBaseLoader();
		if(!docPerFile){
			baseLoader2.setDocumentStyle(MyTextBaseLoader.DOC_PER_LINE);
		}
		baseLoader2.setBeginTag(beginTagString);
		baseLoader2.setEndTag(endTagString);
		baseLoader2.load(file);
		MutableTextLabels labels2 = baseLoader2.getLabels();
		return labels2;
	}
	
	private void printRecall(MutableTextLabels taggedText, MutableTextLabels result, List<String> attributes){
		int correctResults = 0;
		int totalResults = 0;
		for(String attribute : attributes){
			List<Span> taggedTextResults = new ArrayList<Span>();
			List<Span> untaggedTextResults = new ArrayList<Span>();
			
			//Criar lista para tagged
			Iterator<Span> iterTagged = taggedText.getTextBase().documentSpanIterator();
			while(iterTagged.hasNext()){
				for(Span text : taggedText.getTypeSet(attribute, iterTagged.next().getDocumentId())){
					taggedTextResults.add(text);
				}
			}

			
			//Criar lista para untagged
			Iterator<Span> iterUntagged = result.getTextBase().documentSpanIterator();
			while(iterUntagged.hasNext()){
				for(Span text : result.getTypeSet(attribute, iterUntagged.next().getDocumentId())){
					untaggedTextResults.add(text);
				}
			}
			
			for(Span expected : taggedTextResults){
				if(untaggedTextResults.contains(expected)){
					correctResults++;
				}else{
					System.out.println("Recall Error: " + expected);
				}
				totalResults++;
			}
		}
		System.out.println("Recall report:");
		System.out.println("Correct results: " + correctResults);
		System.out.println("Expected results: " + totalResults);
		System.out.println("Recall: " + ((float)correctResults)/((float)totalResults));
	}
	
	private float getRecallValue(MutableTextLabels taggedText, MutableTextLabels result, List<String> attributes){
		int correctResults = 0;
		int totalResults = 0;
		for(String attribute : attributes){
			int localCorrectResults=0;
			int localTotalResults=0;
			List<Span> taggedTextResults = new ArrayList<Span>();
			List<Span> untaggedTextResults = new ArrayList<Span>();
			
			//Criar lista para tagged
			Iterator<Span> iterTagged = taggedText.getTextBase().documentSpanIterator();
			while(iterTagged.hasNext()){
				for(Span text : taggedText.getTypeSet(attribute, iterTagged.next().getDocumentId())){
					taggedTextResults.add(text);
				}
			}

			
			//Criar lista para untagged
			Iterator<Span> iterUntagged = result.getTextBase().documentSpanIterator();
			while(iterUntagged.hasNext()){
				for(Span text : result.getTypeSet(attribute, iterUntagged.next().getDocumentId())){
					untaggedTextResults.add(text);
				}
			}
			
			for(Span expected : taggedTextResults){
				if(untaggedTextResults.contains(expected)){
					correctResults++;
					localCorrectResults++;
				}
				totalResults++;
				localTotalResults++;
			}
			System.out.println("Recall of " +attribute + ": " + ((float)localCorrectResults)/((float)localTotalResults));
		}
		return ((float)correctResults)/((float)totalResults);
	}
	
	private void printPrecision(MutableTextLabels taggedText, MutableTextLabels result, List<String> attributes){
		int correctResults = 0;
		int totalResults = 0;
		for(String attribute : attributes){
			List<Span> taggedTextResults = new ArrayList<Span>();
			List<Span> untaggedTextResults = new ArrayList<Span>();
			
			//Criar lista para tagged
			Iterator<Span> iterTagged = taggedText.getTextBase().documentSpanIterator();
			while(iterTagged.hasNext()){
				for(Span text : taggedText.getTypeSet(attribute, iterTagged.next().getDocumentId())){
					taggedTextResults.add(text);
				}
			}
			
			//Criar lista para untagged
			Iterator<Span> iterUntagged = result.getTextBase().documentSpanIterator();
			while(iterUntagged.hasNext()){
				for(Span text : result.getTypeSet(attribute, iterUntagged.next().getDocumentId())){
					untaggedTextResults.add(text);
				}
			}
			
			for(Span tagged : untaggedTextResults){
				if(taggedTextResults.contains(tagged)){
					correctResults++;
				}else{
					System.out.println("Precision Error: " + tagged);
				}
				totalResults++;
			}
		}
		System.out.println("Precision report:");
		System.out.println("Correct results: " + correctResults);
		System.out.println("Total classified: " + totalResults);
		System.out.println("Precision: " + ((float)correctResults)/((float)totalResults));
	}
	
	private float getPrecisionValue(MutableTextLabels taggedText, MutableTextLabels result, List<String> attributes){
		int correctResults = 0;
		int totalResults = 0;
		for(String attribute : attributes){
			int localCorrectResults=0;
			int localTotalResults=0;
			List<Span> taggedTextResults = new ArrayList<Span>();
			List<Span> untaggedTextResults = new ArrayList<Span>();
			
			//Criar lista para tagged
			Iterator<Span> iterTagged = taggedText.getTextBase().documentSpanIterator();
			while(iterTagged.hasNext()){
				for(Span text : taggedText.getTypeSet(attribute, iterTagged.next().getDocumentId())){
					taggedTextResults.add(text);
				}
			}
			
			//Criar lista para untagged
			Iterator<Span> iterUntagged = result.getTextBase().documentSpanIterator();
			while(iterUntagged.hasNext()){
				for(Span text : result.getTypeSet(attribute, iterUntagged.next().getDocumentId())){
					untaggedTextResults.add(text);
				}
			}
			
			for(Span tagged : untaggedTextResults){
				if(taggedTextResults.contains(tagged)){
					correctResults++;
					localCorrectResults++;
				}
				totalResults++;
				localTotalResults++;
			}
			System.out.println("Precision of " +attribute + ": " + ((float)localCorrectResults)/((float)localTotalResults));
		}
		return ((float)correctResults)/((float)totalResults);
	}
	
	public void setBeginTag(String str){
		this.beginTagString = str;
	}
	
	public void setEndTag(String str){
		this.endTagString = str;
	}
	
	public void setDocPerFile(boolean docPerFile){
		this.docPerFile = docPerFile;
	}
}
